[id].vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <div class="admin--showroom-form">
  3. <div v-if="isLoading" class="admin--loading">데이터를 불러오는 중...</div>
  4. <form v-else @submit.prevent="handleSubmit" class="admin--form">
  5. <!-- 전시장명 -->
  6. <div class="admin--form-group">
  7. <label class="admin--form-label"
  8. >전시장명 <span class="admin--required">*</span></label
  9. >
  10. <input
  11. v-model="formData.name"
  12. type="text"
  13. class="admin--form-input"
  14. placeholder="전시장명을 입력하세요"
  15. required
  16. />
  17. </div>
  18. <!-- 소속명 선택 -->
  19. <div class="admin--form-group">
  20. <label class="admin--form-label"
  21. >소속명 <span class="admin--required">*</span></label
  22. >
  23. <select v-model="formData.branch_id" class="admin--form-select" required>
  24. <option value="">소속 지점을 선택하세요</option>
  25. <option v-for="branch in branches" :key="branch.id" :value="branch.id">
  26. {{ branch.name }}
  27. </option>
  28. </select>
  29. </div>
  30. <!-- 대표번호 -->
  31. <div class="admin--form-group">
  32. <label class="admin--form-label"
  33. >대표번호 <span class="admin--required">*</span></label
  34. >
  35. <input
  36. v-model="formData.phone"
  37. type="tel"
  38. class="admin--form-input"
  39. placeholder="02-1234-5678"
  40. required
  41. />
  42. </div>
  43. <!-- 주소 -->
  44. <div class="admin--form-group">
  45. <label class="admin--form-label"
  46. >주소 <span class="admin--required">*</span></label
  47. >
  48. <input
  49. v-model="formData.address"
  50. type="text"
  51. class="admin--form-input"
  52. placeholder="주소를 입력하세요"
  53. required
  54. />
  55. </div>
  56. <!-- 상세주소 -->
  57. <div class="admin--form-group">
  58. <label class="admin--form-label">상세주소</label>
  59. <input
  60. v-model="formData.detail_address"
  61. type="text"
  62. class="admin--form-input"
  63. placeholder="상세주소를 입력하세요"
  64. />
  65. </div>
  66. <!-- 위도/경도 -->
  67. <div class="admin--form-group">
  68. <label class="admin--form-label">위치 좌표</label>
  69. <div class="admin--coordinate-group">
  70. <div class="admin--coordinate-item">
  71. <label>위도</label>
  72. <input
  73. v-model.number="formData.latitude"
  74. type="text"
  75. step="any"
  76. class="admin--form-input"
  77. placeholder="37.5665"
  78. />
  79. </div>
  80. <div class="admin--coordinate-item">
  81. <label>경도</label>
  82. <input
  83. v-model.number="formData.longitude"
  84. type="text"
  85. step="any"
  86. class="admin--form-input"
  87. placeholder="126.9780"
  88. />
  89. </div>
  90. </div>
  91. </div>
  92. <!-- 영업시간 -->
  93. <div class="admin--form-group">
  94. <label class="admin--form-label">영업시간</label>
  95. <textarea
  96. v-model="formData.business_hours"
  97. class="admin--form-textarea"
  98. rows="3"
  99. placeholder="평일: 09:00 - 18:00&#10;주말: 10:00 - 17:00"
  100. ></textarea>
  101. </div>
  102. <!-- 견적요청 링크 -->
  103. <div class="admin--form-group">
  104. <label class="admin--form-label">견적요청 링크</label>
  105. <input
  106. v-model="formData.quote_link"
  107. type="url"
  108. class="admin--form-input"
  109. placeholder="https://example.com/quote"
  110. />
  111. </div>
  112. <!-- 시승신청 링크 -->
  113. <div class="admin--form-group">
  114. <label class="admin--form-label">시승신청 링크</label>
  115. <input
  116. v-model="formData.test_drive_link"
  117. type="url"
  118. class="admin--form-input"
  119. placeholder="https://example.com/test-drive"
  120. />
  121. </div>
  122. <!-- 링크 관리 -->
  123. <div class="admin--form-group">
  124. <label class="admin--form-label">관련 링크</label>
  125. <div class="admin--multi-input-wrapper">
  126. <div
  127. v-for="(link, index) in formData.links"
  128. :key="index"
  129. class="admin--multi-input-item"
  130. >
  131. <input
  132. v-model="formData.links[index]"
  133. type="url"
  134. class="admin--form-input"
  135. placeholder="https://example.com"
  136. />
  137. <button type="button" class="admin--btn-remove" @click="removeLink(index)">
  138. 삭제
  139. </button>
  140. </div>
  141. <button type="button" class="admin--btn-add" @click="addLink">
  142. + 링크 추가
  143. </button>
  144. </div>
  145. </div>
  146. <!-- 버튼 영역 -->
  147. <div class="admin--form-actions">
  148. <button type="submit" class="admin--btn admin--btn-primary" :disabled="isSaving">
  149. {{ isSaving ? "저장 중..." : "확인" }}
  150. </button>
  151. <button type="button" class="admin--btn admin--btn-secondary" @click="goToList">
  152. 목록
  153. </button>
  154. </div>
  155. <!-- 성공/에러 메시지 -->
  156. <div v-if="successMessage" class="admin--alert admin--alert-success">
  157. {{ successMessage }}
  158. </div>
  159. <div v-if="errorMessage" class="admin--alert admin--alert-error">
  160. {{ errorMessage }}
  161. </div>
  162. </form>
  163. </div>
  164. </template>
  165. <script setup>
  166. import { ref, onMounted } from "vue";
  167. import { useRouter, useRoute } from "vue-router";
  168. definePageMeta({
  169. layout: "admin",
  170. middleware: ["auth"],
  171. });
  172. const router = useRouter();
  173. const route = useRoute();
  174. const { get, put } = useApi();
  175. const isLoading = ref(true);
  176. const isSaving = ref(false);
  177. const successMessage = ref("");
  178. const errorMessage = ref("");
  179. const branches = ref([]);
  180. const formData = ref({
  181. name: "",
  182. branch_id: "",
  183. phone: "",
  184. address: "",
  185. detail_address: "",
  186. latitude: null,
  187. longitude: null,
  188. business_hours: "",
  189. quote_link: "",
  190. test_drive_link: "",
  191. links: [],
  192. });
  193. // 지점 목록 로드
  194. const loadBranches = async () => {
  195. const { data, error } = await get("/branch/list", { params: { per_page: 1000 } });
  196. if (data?.success && data?.data?.items) {
  197. branches.value = data.data.items.filter((branch) => branch.is_active == 1);
  198. }
  199. };
  200. // 전시장 데이터 로드
  201. const loadShowroom = async () => {
  202. const id = route.params.id;
  203. const { data, error } = await get(`/showroom/${id}`);
  204. if (data?.success && data?.data) {
  205. const showroom = data.data;
  206. formData.value = {
  207. name: showroom.name || "",
  208. branch_id: showroom.branch_id || "",
  209. phone: showroom.main_phone || "",
  210. address: showroom.address || "",
  211. detail_address: showroom.detail_address || "",
  212. latitude: showroom.latitude || null,
  213. longitude: showroom.longitude || null,
  214. business_hours: showroom.business_hours || "",
  215. quote_link: showroom.quote_link || "",
  216. test_drive_link: showroom.test_drive_link || "",
  217. links: showroom.links && showroom.links.length > 0 ? showroom.links : [""],
  218. };
  219. }
  220. isLoading.value = false;
  221. };
  222. // 링크 추가
  223. const addLink = () => {
  224. formData.value.links.push("");
  225. };
  226. // 링크 삭제
  227. const removeLink = (index) => {
  228. formData.value.links.splice(index, 1);
  229. };
  230. // 폼 제출
  231. const handleSubmit = async () => {
  232. successMessage.value = "";
  233. errorMessage.value = "";
  234. // 유효성 검사
  235. if (!formData.value.name) {
  236. errorMessage.value = "전시장명을 입력하세요.";
  237. return;
  238. }
  239. if (!formData.value.branch_id) {
  240. errorMessage.value = "소속 지점을 선택하세요.";
  241. return;
  242. }
  243. if (!formData.value.phone) {
  244. errorMessage.value = "대표번호를 입력하세요.";
  245. return;
  246. }
  247. if (!formData.value.address) {
  248. errorMessage.value = "주소를 입력하세요.";
  249. return;
  250. }
  251. isSaving.value = true;
  252. try {
  253. const id = route.params.id;
  254. // 빈 링크 제거
  255. const submitData = {
  256. ...formData.value,
  257. links: formData.value.links.filter((link) => link.trim() !== ""),
  258. };
  259. const { data, error } = await put(`/showroom/${id}`, submitData);
  260. if (error || !data?.success) {
  261. errorMessage.value = error?.message || data?.message || "수정에 실패했습니다.";
  262. } else {
  263. successMessage.value = data.message || "전시장이 수정되었습니다.";
  264. setTimeout(() => {
  265. router.push("/site-manager/showroom/list");
  266. }, 1000);
  267. }
  268. } catch (error) {
  269. errorMessage.value = "서버 오류가 발생했습니다.";
  270. console.error("Save error:", error);
  271. } finally {
  272. isSaving.value = false;
  273. }
  274. };
  275. // 목록으로 이동
  276. const goToList = () => {
  277. router.push("/site-manager/showroom/list");
  278. };
  279. onMounted(async () => {
  280. await loadBranches();
  281. await loadShowroom();
  282. });
  283. </script>
  284. <style scoped>
  285. .admin--coordinate-group {
  286. display: flex;
  287. gap: 16px;
  288. }
  289. .admin--coordinate-item {
  290. flex: 1;
  291. }
  292. .admin--coordinate-item label {
  293. display: block;
  294. margin-bottom: 8px;
  295. font-size: 14px;
  296. color: #666;
  297. }
  298. </style>